using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ICSharpCode.NRefactory.Visitors;
using AstHelpers;
using ICSharpCode.NRefactory.Ast;
using Genuilder;

namespace InterfaceExtractor.Targets
{
	public class InterfaceExtractorGenerator : AbstractAstVisitor, ICodeGenerator
	{
		public class Context
		{
			public Stack<NamespaceDeclaration> NsStack = new Stack<NamespaceDeclaration>();
			public Stack<TypeDeclaration> TypeStack = new Stack<TypeDeclaration>();
			public TypeDeclaration CurrentType
			{
				get
				{
					return TypeStack.Peek();
				}
			}
			public bool HasCurrentType
			{
				get
				{
					return TypeStack.Count > 0;
				}
			}
			public NamespaceDeclaration CurrentNamespace
			{
				get
				{
					return NsStack.Peek();
				}
			}
			public bool HasCurrentNamespace
			{
				get
				{
					return NsStack.Count > 0;
				}
			}
		}
		private readonly CompilationUnit _CompilationUnit = new CompilationUnit();
		public CompilationUnit CompilationUnit
		{
			get
			{
				return _CompilationUnit;
			}
		}

		public InterfaceExtractorGenerator()
		{
			_CompilationUnit = new CompilationUnit();
			_UsingDeclaration = new UsingDeclaration(new List<Using>());
			_CompilationUnit.Children.Add(_UsingDeclaration);
		}
		Context context = new Context();
		UsingDeclaration _UsingDeclaration;
		void EnsureUsingIsPresent(Using @using)
		{
			if(!_UsingDeclaration.Usings.Any(u => u.Name == @using.Name))
			{
				_UsingDeclaration.Usings.Add(@using);
			}
		}

		public override object VisitCompilationUnit(CompilationUnit compilationUnit, object data)
		{
			var newUsings = compilationUnit.Children.OfType<UsingDeclaration>().SelectMany(d => d.Usings).ToList();
			foreach(var newUsing in newUsings)
			{
				EnsureUsingIsPresent(newUsing);
			}
			return base.VisitCompilationUnit(compilationUnit, data);
		}


		public static string GetGeneratedTypeNameFor(TypeDeclaration declaration)
		{
			var attributes = declaration.Attributes.SelectMany(s => s.Attributes);
			var extractInterface = attributes.FirstOrDefault(a => a.Name.EndsWith(typeof(ExtractInterface).Name));
			if(extractInterface == null)
				return null;
			var typeName = extractInterface.PositionalArguments.Count > 0 ? ((TypeOfExpression)extractInterface.PositionalArguments[0]).TypeReference.Type : "I" + declaration.Name;
			return typeName;
		}

		public override object VisitTypeDeclaration(ICSharpCode.NRefactory.Ast.TypeDeclaration typeDeclaration, object data)
		{
			if(context.HasCurrentType)	//Does not support nested type yet
				return null;


			//var attributes = typeDeclaration.Attributes.SelectMany(s => s.Attributes);
			//var extractInterface = attributes.FirstOrDefault(a => a.Name.EndsWith(typeof(ExtractInterface).Name));
			//if(extractInterface == null)
			//    return null;
			var typeName = GetGeneratedTypeNameFor(typeDeclaration);
			if(typeName == null)
				return null;

			var metadataModifiers = (typeDeclaration.Modifier & (Modifiers)15); //same visibility


			var metadataType = new TypeDeclaration(metadataModifiers, new List<AttributeSection>());//typeDeclaration.Attributes);
			metadataType.Type = ClassType.Interface;
			metadataType.Name = typeName;
			metadataType.Templates = typeDeclaration.Templates;


			if(context.HasCurrentNamespace)
			{
				context.CurrentNamespace.AddChild(metadataType);
			}
			else
			{
				_CompilationUnit.AddChild(metadataType);
			}
			//if(typeDeclaration.BaseTypes != null && typeDeclaration.BaseTypes.Count > 0)
			//{
			//    type.BaseTypes = new List<TypeReference>();
			//    type.BaseTypes.Add(new TypeReference(typeDeclaration.BaseTypes[0].Type + "Metadata"));
			//}
			context.TypeStack.Push(metadataType);
			base.VisitTypeDeclaration(typeDeclaration, data);
			context.TypeStack.Pop();

			return null;
		}

		public override object VisitNamespaceDeclaration(ICSharpCode.NRefactory.Ast.NamespaceDeclaration namespaceDeclaration, object data)
		{
			context.NsStack.Push(new NamespaceDeclaration(namespaceDeclaration.Name));
			base.VisitNamespaceDeclaration(namespaceDeclaration, data);
			_CompilationUnit.AddChild(context.CurrentNamespace);
			context.NsStack.Pop();
			return null;
		}

		public override object VisitMethodDeclaration(MethodDeclaration methodDeclaration, object data)
		{
			if((methodDeclaration.Modifier & (Modifiers)15) != Modifiers.Public)
			{
				return null;
			}

			var currentType = context.CurrentType;
			var methodInterface = new MethodDeclaration();
			methodInterface.Name = methodDeclaration.Name;
			methodInterface.Modifier = Modifiers.None;
			methodInterface.Parameters = methodDeclaration.Parameters;
			methodInterface.Templates = methodDeclaration.Templates;
			methodInterface.TypeReference = methodDeclaration.TypeReference;

			currentType.AddChild(methodInterface);
			return null;
		}


		public override object VisitEventDeclaration(EventDeclaration eventDeclaration, object data)
		{
			var currentType = context.CurrentType;
			if((eventDeclaration.Modifier & (Modifiers)15) != Modifiers.Public)
			{
				return null;
			}

			var eventInterface = new EventDeclaration();//(Modifiers.None, new List<AttributeSection>(), propertyDeclaration.Name, new List<ParameterDeclarationExpression>());
			eventInterface.Name = eventDeclaration.Name;
			eventInterface.TypeReference = eventDeclaration.TypeReference;


			currentType.AddChild(eventInterface);

			return null;
		}


		public override object VisitPropertyDeclaration(ICSharpCode.NRefactory.Ast.PropertyDeclaration propertyDeclaration, object data)
		{
			if((propertyDeclaration.Modifier & (Modifiers)15) != Modifiers.Public)
			{
				return null;
			}

			var currentType = context.CurrentType;
			var propertyInterface = new PropertyDeclaration(Modifiers.None, new List<AttributeSection>(), propertyDeclaration.Name, new List<ParameterDeclarationExpression>());
			propertyInterface.TypeReference = propertyDeclaration.TypeReference;
			propertyInterface.GetRegion = PropertyGetRegion.Null;
			propertyInterface.SetRegion = PropertySetRegion.Null;

			if(propertyDeclaration.HasGetRegion && (propertyDeclaration.GetRegion.Modifier.HasSameVisibility(Modifiers.None)))
			{
				propertyInterface.GetRegion = new PropertyGetRegion(BlockStatement.Null, null);
			}
			if(propertyDeclaration.HasSetRegion && (propertyDeclaration.SetRegion.Modifier.HasSameVisibility(Modifiers.None)))
			{
				propertyInterface.SetRegion = new PropertySetRegion(BlockStatement.Null, null);
			}

			currentType.AddChild(propertyInterface);
			return null;
		}
	}



}
